#include "patternview.h"
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include "patterneditor.h"
#include "song.h"
#include "track.h"
#include "songitem.h"
#include "pattern.h"
#include "event.h"
#include "app.h"
#include "keybindings.h"
#include "rowlabel.h"
#include "columnlabel.h"
#include "font.h"
using namespace std;

PatternView::PatternView(App *pApp, Rect rect, Font *pFont) 
: EditorView(pApp, rect, pFont), m_playerRow(EditorView::NoRow), m_playing(false) {

     int rowCount = m_pEditor->song()->patternLength();
     m_pRowLabel->setCount(rowCount);
     m_pRowLabel->setHighlight(m_pEditor->song()->rowsPerBeat());

     m_vScroll.setMaximum(rowCount);
     m_vScroll.setMinimum(wholeVisibleRows());
     setVScrollPos(m_vScroll.minimum());
}

void PatternView::paintEvent(Event *pEvent, Rect rect, bool highlight) {

     int w=0, h=0;
     Song *pSong = m_pEditor->song();
     if (!pSong)
	  return;

     int charx=0, charw=0;
     const ALLEGRO_COLOR cursorColor = m_pApp->skin()->color(Skin::CursorColor);

     // highlight if cursor col/row is editor's current one
     if (pEvent == m_pEditor->curEvent()) {
	  switch (m_pEditor->curField()) {
	  case PatternEditor::Note:
	       charx = 0;
	       charw = 3;
	       break;
	  case PatternEditor::CCNum0:
	       charx = 1;
	       charw = 1;
	       break;
	  case PatternEditor::CCNum1:
	       charx = 2;
	       charw = 1;
	       break;
	  case PatternEditor::Instrument0:
	       charx = 3;
	       charw = 1;
	       break;
	  case PatternEditor::Instrument1:
	       charx = 4;
	       charw = 1;
	       break;
	  case PatternEditor::Val0:
	       charx = 5;
	       charw = 1;
	       break;
	  case PatternEditor::Val1:
	       charx = 6;
	       charw = 1;
	       break;
	  case PatternEditor::Effect:
	       charx = 7;
	       charw = 1;
	       break;
	  case PatternEditor::EffectArg0:
	       charx = 8;
	       charw = 1;
	       break;
	  case PatternEditor::EffectArg1:
	       charx = 9;
	       charw = 1;
	       break;
	  case PatternEditor::NoField:
	       break;
	  }
	  int x1 = rect.x() + m_pFont->width() * charx + 2;
	  int y1 = rect.y();
	  int x2 = x1 + m_pFont->width()*charw;
	  int y2 = y1 + m_pFont->height();
	  al_draw_filled_rectangle(x1,y1,x2,y2,cursorColor);
     }
     // make sure event paints within rect
     w = m_pFont->width() * Event::strlen;
     if (w > rect.width())
	  w = rect.width();
     h = m_pFont->height();
     if (h > rect.height())
	  h = rect.height();
     int x = rect.x() + 2;
     w -= 2;
     // setup colors
     ALLEGRO_COLOR white = al_map_rgb(255,255,255);
     ALLEGRO_COLOR fieldColor;
     ALLEGRO_COLOR fieldHighlight;
     if (highlight) {
	  fieldColor = m_pApp->skin()->color(Skin::HighlightColor2);
	  fieldHighlight = m_pApp->skin()->color(Skin::HighlightColor);
     } else {
	  fieldColor = m_pApp->skin()->color(Skin::FgColor);
	  fieldHighlight = m_pApp->skin()->color(Skin::HighlightColor);
     }
     // paint event
     al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, fieldColor);
     m_pFont->print(pEvent->numStr(), Rect(x, rect.y(), w, h));
     x += m_pFont->width() * 3;
     w -= m_pFont->width() * 3;
     al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, fieldHighlight);
     m_pFont->print(pEvent->instNumStr(), Rect(x, rect.y(), w, h));
     x += m_pFont->width() * 2;
     w -= m_pFont->width() * 2;
     al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, fieldColor);
     m_pFont->print(pEvent->valStr(), Rect(x, rect.y(), w, h));
     x += m_pFont->width() * 2;
     w -= m_pFont->width() * 2;
     al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, fieldHighlight);
     m_pFont->print(pEvent->effectStr(), Rect(x, rect.y(), w, h));
     al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, white);
}

void PatternView::paintTrack(int col, Rect rect) {

     int colwidth = colWidth();
     int visrows = visibleRows();
     int linex=0, y=0, h=0;
     Song *pSong = m_pEditor->song();
     if (!pSong)
	  return;

     Track *pTrack = NULL;
     SongItem *pItem = NULL;
     Pattern *pPat = NULL;
     Event *pEvent = NULL;
     const ALLEGRO_COLOR cursorColor = m_pApp->skin()->color(Skin::CursorColor);

     // get pattern to draw
     pTrack = pSong->trackAt(col);
     if (!pTrack)
	  return;
     pItem = pTrack->songItemAt(m_pEditor->curSongItemNum());
     if (!pItem)
	  return;
     // draw track separator if inside rect
     linex = rect.x() + colwidth;
     if (linex <= rect.x() + rect.width()) {
	  al_draw_line(linex, rect.y(), linex, rect.y() + rect.height(),
		       al_map_rgb(255,255,255),
		       EditorView::LineThickness);
     }
     pPat = pItem->pattern();
     if (!pPat)
	  return;
     // draw rows
     bool highlight = false;
     Rect rowRect(0,0,0,0);
     for (int row=0; row<visrows; row++) {
	  if (row >= pPat->length())
	       break;
	  pEvent = pPat->eventAt(m_rowOffset+row);
	  if (!pEvent)
	       continue;
	  y = rect.y() + row * m_pFont->height();
	  if (y > rect.y() + rect.height())
	       break;
	  h = m_pFont->height();
	  if (y + h > rect.y() + rect.height())
	       h = rect.y() + rect.height() - y;
	  rowRect.setX(rect.x());
	  rowRect.setY(y);
	  rowRect.setWidth(rect.width());
	  rowRect.setHeight(h);
	  highlight = !((m_rowOffset + row) % m_pEditor->song()->rowsPerBeat());
	  // highlight if this row is the one being played
	  if (row + m_rowOffset == m_playerRow) {
	       ALLEGRO_COLOR c;
	       if (m_playing)
		    c = m_pApp->skin()->color(Skin::PlayingColor);
	       else
		    c = m_pApp->skin()->color(Skin::StoppedColor);
	       al_draw_filled_rectangle(rowRect.x() + 1, rowRect.y(), 
					rowRect.x() + rowRect.width(), 
					rowRect.y() + rowRect.height(), c);
	  }
	  paintEvent(pEvent, rowRect, highlight);
     }
}

void PatternView::paintPattern(Rect rect) {

     int colwidth = colWidth();
//     int viscols = ceil((float)rect.width() / (float)colwidth);
     int viscols = visibleCols();
     int x=0, w=0;

     for (int col=0; col<viscols; col++) {
	  x = rect.x() + col * colwidth;
	  w = colwidth;
	  if (x + w > rect.x() + rect.width())
	       w = rect.x() + rect.width() - x;
	  paintTrack(col+m_colOffset, Rect(x, rect.y(), w, rect.height()));
     }
     
}

void PatternView::setRect(Rect rect) {
     m_rect = rect;
     if (m_pEditor->curTrackNum() * colWidth() > m_viewRect.width())
	  m_pEditor->setCurTrackNum(m_colOffset + wholeVisibleCols() - 1);
     if (m_pEditor->curRowNum() * m_pFont->height() > m_viewRect.height())
	  m_pEditor->setCurRowNum(m_rowOffset + wholeVisibleRows() - 1);
     updateRects();
}

void PatternView::paint(bool all) {
     // clear
     al_draw_filled_rectangle(m_rect.x(), m_rect.y(), 
			      m_rect.x() + m_rect.width(), 
			      m_rect.y() + m_rect.height(),
			      al_map_rgb(0,0,0));
     // draw scrollbars
     m_hScroll.paint(all);
     m_vScroll.paint(all);
     // draw row labels
     m_pRowLabel->paint();
     // draw column labels
     m_pColLabel->paint();
     // draw pattern
     paintPattern(m_viewRect);
     m_needPaint = false;
}

void PatternView::decRow() {
     m_pEditor->setCurRowNum(m_pEditor->curRowNum() - 1);
     if (m_pEditor->curRowNum() - m_rowOffset < 0)
	  setRowOffset(m_rowOffset-1);
}

void PatternView::incRow() {
     m_pEditor->setCurRowNum(m_pEditor->curRowNum() + 1);
     if (m_pEditor->curRowNum() - m_rowOffset >= wholeVisibleRows())
	  setRowOffset(m_rowOffset+1);
}

void PatternView::keyDown(int keycode, int modifiers, int unichar) {
     int action = m_pApp->keyBindings()->keyAction(keycode, modifiers);
     int viscols = wholeVisibleCols();
     Event *pEvent = m_pEditor->curEvent();
     int note=0, inst=0, val=0;
     switch (action) {
     case KeyBindings::RowDown:
	  decRow();
	  refresh();
 	  break;
     case KeyBindings::RowUp:
	  incRow();
	  refresh();
 	  break;
     case KeyBindings::FieldDown:
 	  m_pEditor->decField();
	  if (m_pEditor->curTrackNum() - m_colOffset < 0)
	       setColOffset(m_colOffset-1);
	  refresh();
 	  break;
     case KeyBindings::FieldUp:
 	  m_pEditor->incField();
	  if (m_pEditor->curTrackNum() - m_colOffset >= viscols)
	       setColOffset(m_colOffset+1);
	  refresh();
 	  break;
     case KeyBindings::EventType:
	  if (m_pEditor->curField() == PatternEditor::Note ||
	      m_pEditor->curField() == PatternEditor::CCNum0 ||
	      m_pEditor->curField() == PatternEditor::CCNum1)
	       toggleEventType();
 	  break;
     case KeyBindings::NoteOff:
	  if (m_pEditor->curField() == PatternEditor::Note && pEvent) {
	       inst = pEvent->instNum();
	       if (inst == Event::NoInst)
		    inst = m_pEditor->curInstNum();
	       pEvent->setEvent(Event::NoteOff, inst, Event::NoAmp);
	       incRow();
	       refresh();
	  }
	  break;
     case KeyBindings::Delete:
	  if (!pEvent)
	       break;
	  if (m_pEditor->curField() == PatternEditor::Note ||
	      m_pEditor->curField() == PatternEditor::CCNum0 ||
	      m_pEditor->curField() == PatternEditor::CCNum1) {
	       pEvent->clear();
	       incRow();
	       refresh();
	  }
	  break;
     default:
	  if (!pEvent)
	       break;
	  if (m_pEditor->curField() == PatternEditor::Note &&
	      action >= KeyBindings::NoteC0 && action <= KeyBindings::NoteB1) {
	       inst = pEvent->instNum();
	       if (inst == Event::NoInst)
		    inst = m_pEditor->curInstNum();
	       note = m_pEditor->octave() * 12 + (action - KeyBindings::NoteC0);
	       pEvent->setEvent(note, inst, PatternEditor::Velocity);
	       m_pApp->player()->playNote(note, pEvent->instNum());
	       incRow();
	       refresh();
	       break;
	  } else if (m_pEditor->curField() == PatternEditor::CCNum0 && 
		     KeyBindings::keyIsHex(keycode)) {
	       pEvent->setNumber((pEvent->number() & 0x0f) + 
				 (KeyBindings::hexKeyInt(keycode) << 4));
	       m_pEditor->incField();
	       refresh();
	       break;
	  } else if (m_pEditor->curField() == PatternEditor::CCNum1 && 
		     KeyBindings::keyIsHex(keycode)) {
	       pEvent->setNumber((pEvent->number() & 0xf0) + 
				 (KeyBindings::hexKeyInt(keycode) & 0x0f));
	       incRow();
	       refresh();
	       break;
	  } else if (m_pEditor->curField() == PatternEditor::Instrument0 && 
		     KeyBindings::keyIsHex(keycode)) {
	       inst = pEvent->instNum() + 1;
	       if (inst == Event::NoInst)
		    inst = 1;
	       inst = (inst & 0x0f) + 
		    (KeyBindings::hexKeyInt(keycode) << 4);
	       if (inst >= m_pEditor->song()->instrumentCount())
		    inst = m_pEditor->song()->instrumentCount();
	       inst = max(0,inst - 1);
	       pEvent->setInstNum(inst);
	       m_pEditor->setCurInstNum(inst);
	       m_pEditor->incField();
	       refresh();
	       break;
	  } else if (m_pEditor->curField() == PatternEditor::Instrument1 &&
		     KeyBindings::keyIsHex(keycode)) {
	       inst = pEvent->instNum() + 1;
	       if (inst == Event::NoInst)
		    inst = 1;
	       inst = (inst & 0xf0) + (KeyBindings::hexKeyInt(keycode) & 0x0f);
	       if (inst >= m_pEditor->song()->instrumentCount())
		    inst = m_pEditor->song()->instrumentCount();
	       inst = max(0,inst - 1);
	       pEvent->setInstNum(inst);
	       m_pEditor->setCurInstNum(inst);
	       incRow();
	       refresh();
	       break;
	  } else if (m_pEditor->curField() == PatternEditor::Val0 && 
		     KeyBindings::keyIsHex(keycode)) {
	       val = pEvent->val();
	       if (val == Event::NoAmp)
		    val = 0;
	       pEvent->setVal((val & 0x0f) + 
			      (KeyBindings::hexKeyInt(keycode) << 4));
	       m_pEditor->incField();
	       refresh();
	       break;
	  } else if (m_pEditor->curField() == PatternEditor::Val1 &&
		     KeyBindings::keyIsHex(keycode)) {
	       val = pEvent->val();
	       if (val == Event::NoAmp)
		    val = 0;
	       pEvent->setVal((val & 0xf0) + 
			      (KeyBindings::hexKeyInt(keycode) & 0x0f));
	       incRow();
	       refresh();
	       break;
	  }
	  break;
     }
}

void PatternView::toggleEventType() {
     m_pEditor->toggleCurEventType();
     refresh();
}

int PatternView::playerRow() {
     return m_playerRow;
}

void PatternView::setPlayerRow(int row) {
     m_playerRow = row;
}

bool PatternView::playing() {
     return m_playing;
}

void PatternView::setPlaying(bool playing) {
     m_playing = playing;
}

void PatternView::update() {
     if (m_pApp->player()->songItemNum() == m_pEditor->curSongItemNum()) {
	  if (m_pApp->player()->row() != m_playerRow) {
	       setPlayerRow(m_pApp->player()->row());
	       refresh();
	  }
     } else if (playerRow() != EditorView::NoRow) {
	  setPlayerRow(EditorView::NoRow);
	  refresh();
     }
     if (m_pApp->player()->playing() != m_playing) {
	  setPlaying(m_pApp->player()->playing());
	  refresh();
     }
}
